Using jsPsych and cognition.run

Session 5 - intermediate/advanced

## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
## 
## Attaching package: 'jsonlite'
## 
## 
## The following object is masked from 'package:purrr':
## 
##     flatten

Translations available

Disclaimer: may not be very accurate…


Worksheet overview

Aims

By the end of this worksheet you should be able to:

  • program your own experiments in jsPsych
  • host the experiment online using cognition.run
  • use the participant data for analysis
  • apply the basic skills you have learnt for your own purposes
  • learn some extra skills such as HTML, javascript, CSS and JSON

Pre-requisites

To complete the aims you will need to:

  • follow this worksheet
  • ask questions if you are not sure/be able to google
  • have a working computer and internet connection
  • be patient when things do not work

You do not need to:

  • have any programming knowledge
  • have high computer literacy
  • know anything about jsPsych, cognition.run, html, css or javascript
  • be a linguist

Structure

The worksheet will go through the following sections:

- working with randomisation
- working with data
- working with R to process your data

Recap

In the last session we should have:

  • Use audio and visual stimuli together
  • preload our stimuli
  • create a fully working experiment

Randomisation of positions

One thing that might be useful for an experiment is to completely randomise the position of the stimuli on the screen. In the previous worksheet, we presented three images on the screen, which was predefined in our stimuli file. E.g.

But if we want to fully randomise the order for each participant and trial, we can use some code to make this work.

In JsPsych there are predefined functions we can use that help with randomisation:

jsPsych.randomization.shuffle can be used to shuffle an array.

e.g. if we have [image1, image2, image3], it will present the images in the set order. But if we modify the code to be jsPsych.randomization.shuffle([image1, image2, image3]) we should get a randomised version of this array, e.g. [image3, image2, image3].

We can demonstrate this by modifying the lexical_decision_trial var in our experiment:

var lexical_decision_trial = {
    type: jsPsychAudioButtonResponse,
    stimulus: jsPsych.timelineVariable("audio"),
    choices: jsPsych.randomization.shuffle([
      jsPsych.timelineVariable("image_left"),
      jsPsych.timelineVariable("image_center"),
      jsPsych.timelineVariable("image_right")
      ]),
    prompt: "<p>Click on the picture that matched the sound</p>",
    button_html: '<img src="%choice%" width=100 />',
    data: {
      task: 'lexical_decision',
      image_left: jsPsych.timelineVariable("image_left"),
      image_center: jsPsych.timelineVariable("image_center"),
      image_right: jsPsych.timelineVariable("image_right"), 
      real_word: jsPsych.timelineVariable('real_word'),
      correct_choice: jsPsych.timelineVariable('correct_choice')
    }
};

Now, we should see that the images are randomised in terms of their position, i.e. left, center, right.

However, we now have a problem as we do not know which response is the correct one.

In our stimuli file we explicitly coded the image positions as either left, center, or right. But if we randomise the locations, we lose this information and there is no logging of the order in our data file.

To resolve this we have to adapt our code so that JsPsych logs the order of the images for each trial. We can do this by adding a new parameter to our var:

save_trial_parameters: {
    choices: true
  }
  

This save_trial_parameters parameter will allow us to save any information that is used in our trial. By specifying choices: true we are telling JsPsych to add a new variable to our data that will contain the information used in the choices parameter.

It will look something like this:

["cheese.png","banana.png","sheep.png"]

This provides us with the information about the order in which the images were presented, i.e. the left image was the cheese, the center image the banana and the right image the sheep.

Remember that when a participant makes a choice in this trial, their response will be logged as either 0, 1, or 2, where 0 = left, 1 = center, and 2 = right. In our stimuli file we now have the incorrect value as the positions are randomised, so our correct_choice variable is not longer accurate.

To deal with this we should first create a new stimuli file.

Note we have changed the first three columns so that they do not indicate a position, we have also changed the correct_choice so that it refers to the image, not the index of the image.

Save this file as lexical_decision_stimuli_image_audio_updated.csv

Now we can process this file to a .js using the R code:

paste0("var stimuli = ",
       toJSON(read_delim("lexical_decision_stimuli_image_audio_updated.csv"),
              pretty=TRUE),
       ";") %>%
  write_file(file = "lexical_decision_stimuli_image_audio_updated_JSON.js")

Then we upload it to cognition.run.

Remember to modify your code to replace image_left with image1, image_center with image2, and image_right with image3.

Our lexical_decision_trial code should look like this:

var lexical_decision_trial = {
    type: jsPsychAudioButtonResponse,
    stimulus: jsPsych.timelineVariable("audio"),
    choices: jsPsych.randomization.shuffle([
      jsPsych.timelineVariable("image1"),
      jsPsych.timelineVariable("image2"),
      jsPsych.timelineVariable("image3")
      ]),
    prompt: "<p>Click on the picture that matched the sound</p>",
    button_html: '<img src="%choice%" width=100 />',
    data: {
      task: 'lexical_decision',
      image1: jsPsych.timelineVariable("image1"),
      image2: jsPsych.timelineVariable("image2"),
      image3: jsPsych.timelineVariable("image3"),
      real_word: jsPsych.timelineVariable('real_word'),
      correct_choice: jsPsych.timelineVariable('correct_choice')
    },
  save_trial_parameters: {
    choices: true
  }
};

Our preload code for pushing images should look like this:

for (let i = 0; i < stimuli.length; i++) {
  all_images.push(stimuli[i].image1);
  all_images.push(stimuli[i].image2);
  all_images.push(stimuli[i].image3);
};

Adding a participant ID

If we want to ensure that we can identify out participants as unique individuals, in an anonymised way so that their email address or other demographic information does not need to be shared, we need to add a participant ID to our data.

We can do this with the following code:

jsPsych.data.addProperties({participant_ID: Math.random().toString(36).slice(2)});

This is what the code does:

jsPsych.data.addProperties is a built in JsPsych function that can add variables to all of your rows of data

participant_ID is the name of the variable we will be adding to the data, this is your decision and can be anything that is considered a suitable variable name, i.e. do not use spaces

Math.random() is how we will generate a random alpha-numeric string. ‘Math’ is a built in javascript way to use math related parameters, e.g. if you want to get the value of pi you can use Math.PI. If we use Math.random() it will create a random number < 1 and > 0

.toString(36) is the way we can convert the random number to a value that contains both numeric and alpha characters e.g. instead of 0.12345 it will be 0.1a2b3. The ‘36’ is important, this identifies the types of characters we will have when converting to a string, it is 36 as that is the way to declare base-36, which is just a way to say only include [0-9] and [a-z] values

.slice(2) is how we can remove the first two characters of the string, i.e. the ‘0.’ which is there as Math.random is always < 1

The result will be something like 59f4zwpglkk

I would personally add this code towards the beginning of your script, e.g. after you declare var jsPsych = initJsPsych()

The full experiment

The full experiment code should look like this:

// --------
// Title: Demo experiment
// jsPsych version: 7.3.1
// date: [today]
// author: [your name]
//----------

// inititate jspsych
var jsPsych = initJsPsych();

jsPsych.data.addProperties({participant_ID: Math.random().toString(36).slice(2)});

// full screen
var enter_fullscreen = {
  type: jsPsychFullscreen,
  fullscreen_mode: true,
  message: '<p>some message</p>',
  button_label: 'enter full screen mode',
  data: {
      task: 'fullscreen'
    }
};

// a really simple instructions page with a logo
var instructions_page = {
    type: jsPsychInstructions,
    pages: [
    '<img src="https://sites.ff.cuni.cz/ucjtk/wp-content/uploads/sites/57/2020/11/Ustav_ceske%E2%95%A0%C3%BCho_jazyka_a_teorie_komunikace.svg" width="400" height="140"></br><hr><div style="text-align: left; margin-right: 150px; margin-left: 150px;"><h2>Instructions</h2><p>In this experiment you will see a word on the screen.</p><p><b>If you think the word is a real word press the "d" key, if you do not think it is a real word, press the "h" key.</b></p><p>Press next to continue.</div>',
    'In this experiment you will...'
    ],
    show_clickable_nav: true,
    key_forward: 'Enter',
    key_backward: 'ArrowLeft',
    allow_backward: true,
    show_clickable_nav: true,
    button_label_previous: 'back',
    button_label_next: 'next',
  data: {
      task: 'instructions'
    }
};

// a really bad consent form page
var consent_page = {
    type: jsPsychInstructions,
    pages: [
    'Details of informed consent...',
    ],
    show_clickable_nav: true,
    allow_backward: false,
    button_label_next: 'I consent to participating in this experiment',
  data: {
      task: 'consent'
    }
};

// a demographics page that is very bad
var demographics_page = {
  type: jsPsychSurveyHtmlForm,
  html: '<div style="text-align: left;">'+
  '<p>Question 1 </p><input id="Q1" name="Q1" type="text" required/>'+
  '<p>Question 2 </p><input id="Q2" name="Q2" type="number" />'+
  '<p>Question 3 </p><input id="Q3" name="Q3" type="email" />'+
  '<p>Question 4</p>'+
  '<input type="radio" id="Q4" name="Q4" value="a"><label>a</label><br>'+
  '<input type="radio" id="Q4" name="Q4" value="b"><label>b</label><br>'+
  '<input type="radio" id="Q4" name="Q4" value="c"><label>c</label><br>'+
  '<p>Question 5</p>'+
  '<input type="checkbox" id="Q5" name="Q5" value="a"><label>a</label><br>'+
  '<input type="checkbox" id="Q5" name="Q5" value="b"><label>b</label><br>'+
  '<input type="checkbox" id="Q5" name="Q4" value="c"><label>c</label><br>'+
  '<p>Question 6</p>'+
  `<textarea id="feedback_comments" name="feedback_comments" rows="4" cols="50"/></textarea>`+
  '</br></div>',
  button_label: 'next',
  autofocus: 'Q1',
  data: {
      task: 'demographics'
    }
};

// main trial where images and audio are presented
var lexical_decision_trial = {
    type: jsPsychAudioButtonResponse,
    stimulus: jsPsych.timelineVariable("audio"),
    choices: jsPsych.randomization.shuffle([
      jsPsych.timelineVariable("image1"),
      jsPsych.timelineVariable("image2"),
      jsPsych.timelineVariable("image3")
      ]),
    prompt: "<p>Click on the picture that matched the sound</p>",
    button_html: '<img src="%choice%" width=100 />',
    data: {
      task: 'lexical_decision',
      image1: jsPsych.timelineVariable("image1"),
      image2: jsPsych.timelineVariable("image2"),
      image3: jsPsych.timelineVariable("image3"),
      real_word: jsPsych.timelineVariable('real_word'),
      correct_choice: jsPsych.timelineVariable('correct_choice')
    },
  save_trial_parameters: {
    choices: true
  }
};

// fixation trial
var fixation_trial = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: '<div style="font-size:30pt;">+</div>',
  choices: 'NO_KEYS',
  prompt: '<div style="font-size:10pt; position:relative; bottom:150px;"><br/></div>',
  trial_duration: 2000,
  data: {
      task: 'fixation'
    }
};

// combined fixation and main trial with timeline variables and randomisation
var lexical_decision_combined = {
  timeline: [fixation_trial, lexical_decision_trial],
  timeline_variables: stimuli,
  randomize_order: true
};

// feedback page
var feedback_page = {
  type: jsPsychSurveyHtmlForm,
  html: '<div style="text-align: left;">'+
  '<p>That is the end of the experiment.</p>'+
  '<p>How easy did you find the experiment? </p>'+
  '<input type="radio" id="feedback_ease" name="feedback_ease" value="difficult"><label>difficult</label><br>'+
  '<input type="radio" id="feedback_ease" name="feedback_ease" value="easy"><label>easy</label><br>'+
  '<p>If you have any feedback about the experiment, please write your comments below.</p>'+
  `<textarea id="feedback_comments" name="feedback_comments" rows="4" cols="50"/></textarea>`+
  '</br></div>',
  button_label: 'Submit results',
  autofocus: 'feedback_ease',
  data: {
      task: 'feedback'
    }
};

// get image filenames
var all_images = [];

for (let i = 0; i < stimuli.length; i++) {
  all_images.push(stimuli[i].image1);
  all_images.push(stimuli[i].image2);
  all_images.push(stimuli[i].image3);
};

// get audio filenames
var all_audio = [];

for (let i = 0; i < stimuli.length; i++) {
  all_audio.push(stimuli[i].audio);
};

// preload trial
var preload = {
    type: jsPsychPreload,
    images: [all_images],
    audio: [all_audio],
  data: {
      task: 'preload'
    }
};

// make the timeline array
var timeline = [];

// push the trials to the timeline in this order
timeline.push(preload);
// timeline.push(enter_fullscreen);
timeline.push(instructions_page);
timeline.push(consent_page);
timeline.push(demographics_page);

timeline.push(lexical_decision_combined);

timeline.push(feedback_page);

//run the experiment
jsPsych.run(timeline);

Run through the experiment using the experiment link (e.g. https://lk6wos4ofr.cognition.run) and obtain some data.

Now we can process the data.

Downloading the data

Once you have sent out your link to participants, you will see that there are now some responses in the experiment dashboard, you can download the data using the Download data option:

On the next page you will see some options, set these as:

Data set: all runs

Data encoding: CSV

File format: Zip folder

Individual filename format: participant_ID

Then click the download button

You should now have a .zip folder with all of the data, unzip it and you can access the files.

Processing data in R

First we will be using the following libraries:

library(tidyverse)
library(jsonlite)

tidyverse

See the tidyverse folder for worksheet.

Reading in multiple files

We can return to our experimental data from cognition.run where we have individual files for each participant.

We can use R to read in these files and bind them into one.

First set your working directory to the location where you have the files. We can use the data stored on OneDrive so that everybody is working with the same data.

Change the file path to the week_8 folder

setwd("/Users/james/Library/CloudStorage/OneDrive-Filozofickáfakulta,UniverzitaKarlova/Conducting_experimental_research_online_2024/Lectures/week_8/")

Now we can use some code to read in all the files in the data folder and bind them together

audio_experiment <- list.files(path = "data/",
                               pattern = ".csv",
                               recursive = TRUE,
                               full.names = TRUE) %>% 
    map_df(~read_csv(.x)) %>%
    type_convert()

Here is what the code does:

audio_experiment is the name of the object where we will store the data

<- is the way we assign objects in R

list.files is how we will get all the names of the files we will be processing

path = "data/" is how we specify the folder where the file we want are located

pattern = ".csv" is how we specify that we only want files with .csv in their name

recursive = TRUE is how we will look through folders within the folder for files, e.g. if we had subfolders called ‘new_data’ and ‘old_data’ within our ‘data’ folder the code will look for .csv files in these subfolders, if it is set to FALSE it will only look in the ‘data’ folder and not within any subfolders. This is useful as we do have a subfolder called audio

full.names = TRUE is how we will get the full file path, e.g. instead of just 4cedqr4h6pe.csv, we will have /Users/james/Library/CloudStorage/OneDrive-Filozofickáfakulta,UniverzitaKarlova/Conducting_experimental_research_online_2024/Lectures/week_8/data/audio/4cedqr4h6pe.csv. This makes reading in the files easier

map_df(~read_csv(.x)) is now going to iteratively read in the .csv files using the filenames we got from the previous part of the code. map_df will make sure everything is binded together, ~read_csv will read in the filenames, and .x is where the filenames will be read from, which is a quick way to refer to all the filenames listed in the previous part of the code

type_convert is used to make sure all the columns are in the same format, e.g. participant_ID is always a character variable

Now we should have the data ready to use.

Processing

We can now apply some processing steps to our data.

See if you can understand the code below and interpret what it is doing.

decision_data <- audio_experiment %>%
  filter(task == "lexical_decision") %>%
  select(recorded_at, rt:choices) %>%
  mutate(choice = str_remove_all(choices, '\\"|\\[|\\]')) %>%
  separate(choice, into = c("image_0", "image_1", "image_2"), sep = ",") %>%
  mutate(response_image = ifelse(response == 0, image_0,
                                 ifelse(response == 1, image_1,
                                        ifelse(response == 2, image_2, NA))),
         response_correct = ifelse(response_image == correct_choice, 1, 0))

Convert JSON to columns

Some data will be stored in a JSON format, which is not too easy to use in R.

Look at the data for response when task == "demographics"

audio_experiment %>%
  filter(task == "demographics") %>%
  select(participant_ID, response)

We can convert this JSON data to columns, so that we have data that is easier to use.

We can use this code to make an object that contains the demographic question data:

demographics <- audio_experiment %>%
  filter(task == "demographics") %>%
  select(participant_ID, response) %>%
  mutate(response = map(response, ~ fromJSON(.) %>%
                          as.data.frame())) %>%
  unnest(response)
LS0tCnRpdGxlOiAiVXNpbmcganNQc3ljaCBhbmQgY29nbml0aW9uLnJ1biIKc3VidGl0bGU6ICJTZXNzaW9uIDUgLSBpbnRlcm1lZGlhdGUvYWR2YW5jZWQiCmF1dGhvcjogIkphbWVzIEJyYW5kIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoKICAgIHBhbmRvY19hcmdzOiAiLS1oaWdobGlnaHQtc3R5bGU9bXkudGhlbWUiCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29sbGFwc2VkOiBmYWxzZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBsaWdodGJveDogVFJVRQogICAgZ2FsbGVyeTogVFJVRQogICAgY3NzOiAiY3NzL3N0eWxlLmNzcyIKICAgIAotLS0KCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoanNvbmxpdGUpCmxpYnJhcnkoc2xpY2tSKQpsaWJyYXJ5KGh0bWx0b29scykKbGlicmFyeSh4YXJpbmdhbkV4dHJhKQpsaWJyYXJ5KHJtYXJrZG93bikKbGlicmFyeShmb250YXdlc29tZSkKbGlicmFyeShic3BsdXMpCmxpYnJhcnkoRFQpCgpgYGAKCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBldmFsID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gTkEsCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UpCgprbml0cjo6a25pdF9ob29rcyRzZXQoCiAgbWVzc2FnZSA9IGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICAgICBwYXN0ZSgnPGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJjb2xsYXBzaWJsZTEiPjxzdHJvbmc+JywKICAgICBmYShuYW1lID0gImNpcmNsZS1pbmZvIiksCiAgICAgJyBtb3JlIGluZm88L3N0cm9uZz48L2J1dHRvbj4nLCAnPGRpdiBjbGFzcz0iY29udGVudDEiPjxwPicsCiAgICAgZ3N1YignIyMnLCAnXG4nLCB4KSwKICAgICAnPC9wPjwvZGl2PicsCiAgICAgc2VwID0gJ1xuJykKICAgfSkKCmNvZGVibG9jayA9IGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICAgICBjYXQocGFzdGUoJzxkaXYgY2xhc3M9ImNvZGVibG9jayI+JywKICAgICBwYXN0ZTAoeCksCiAgICAgJzwvZGl2PicsCiAgICAgc2VwID0gJ1xuJykpCiAgIH0KCmBgYAoKLS0tCgojIyBgciBmYSgibGFuZ3VhZ2UiKWAgVHJhbnNsYXRpb25zIGF2YWlsYWJsZQoKRGlzY2xhaW1lcjogbWF5IG5vdCBiZSB2ZXJ5IGFjY3VyYXRlLi4uCgo8ZGl2IGlkPSJnb29nbGVfdHJhbnNsYXRlX2VsZW1lbnQiPjwvZGl2PgoKLS0tCgojIFdvcmtzaGVldCBvdmVydmlldwoKIyMgYHIgZmEoImNyb3NzaGFpcnMiKWAgQWltcwoKQnkgdGhlIGVuZCBvZiB0aGlzIHdvcmtzaGVldCB5b3Ugc2hvdWxkIGJlIGFibGUgdG86CgotICoqcHJvZ3JhbSoqIHlvdXIgb3duIGV4cGVyaW1lbnRzIGluIGpzUHN5Y2gKLSAqKmhvc3QqKiB0aGUgZXhwZXJpbWVudCBvbmxpbmUgdXNpbmcgY29nbml0aW9uLnJ1bgotICoqdXNlKiogdGhlIHBhcnRpY2lwYW50IGRhdGEgZm9yIGFuYWx5c2lzCi0gKiphcHBseSoqIHRoZSBiYXNpYyBza2lsbHMgeW91IGhhdmUgbGVhcm50IGZvciB5b3VyIG93biBwdXJwb3NlcwotICoqbGVhcm4qKiBzb21lIGV4dHJhIHNraWxscyBzdWNoIGFzIEhUTUwsIGphdmFzY3JpcHQsIENTUyBhbmQgSlNPTgoKIyMgYHIgZmEoInVzZXItZ3JhZHVhdGUiKWAgUHJlLXJlcXVpc2l0ZXMKClRvIGNvbXBsZXRlIHRoZSBhaW1zIHlvdSB3aWxsIG5lZWQgdG86CgotICoqZm9sbG93KiogdGhpcyB3b3Jrc2hlZXQKLSAqKmFzayoqIHF1ZXN0aW9ucyBpZiB5b3UgYXJlIG5vdCBzdXJlL2JlIGFibGUgdG8gZ29vZ2xlCi0gKipoYXZlKiogYSB3b3JraW5nIGNvbXB1dGVyIGFuZCBpbnRlcm5ldCBjb25uZWN0aW9uCi0gKipiZSBwYXRpZW50Kiogd2hlbiB0aGluZ3MgZG8gbm90IHdvcmsKCllvdSBkbyBub3QgbmVlZCB0bzoKCi0gaGF2ZSBhbnkgKipwcm9ncmFtbWluZyBrbm93bGVkZ2UqKgotIGhhdmUgaGlnaCAqKmNvbXB1dGVyIGxpdGVyYWN5KioKLSBrbm93IGFueXRoaW5nIGFib3V0ICoqanNQc3ljaCwgY29nbml0aW9uLnJ1biwgaHRtbCwgY3NzIG9yIGphdmFzY3JpcHQqKgotIGJlIGEgKipsaW5ndWlzdCoqCgojIyBgciBmYSgiZm9sZGVyLXRyZWUiKWAgU3RydWN0dXJlCgpUaGUgd29ya3NoZWV0IHdpbGwgZ28gdGhyb3VnaCB0aGUgZm9sbG93aW5nIHNlY3Rpb25zOgoKICAgIC0gd29ya2luZyB3aXRoIHJhbmRvbWlzYXRpb24KICAgIC0gd29ya2luZyB3aXRoIGRhdGEKICAgIC0gd29ya2luZyB3aXRoIFIgdG8gcHJvY2VzcyB5b3VyIGRhdGEKCiMjIGByIGZhKCJsaWdodGJ1bGIiKWAgUmVjYXAKCkluIHRoZSBsYXN0IHNlc3Npb24gd2Ugc2hvdWxkIGhhdmU6CgotIFVzZSBhdWRpbyBhbmQgdmlzdWFsIHN0aW11bGkgdG9nZXRoZXIKLSBwcmVsb2FkIG91ciBzdGltdWxpCi0gY3JlYXRlIGEgZnVsbHkgd29ya2luZyBleHBlcmltZW50CgotLS0KCjwhLS0gIyBXb3JraW5nIHdpdGggcmVzdWx0cyBmaWxlcyBpbiBSIC0tPgoKPCEtLSAjIyBsb2FkaW5nIGluIG11bHRpcGxlIGZpbGVzIHF1aWNrbHkgLS0+Cgo8IS0tICMjIGdldHRpbmcgaW5mb3JtYXRpb24gZnJvbSBkaWZmZXJlbnQgcGFydHMgb2YgdGhlIGV4cGVyaW1lbnQgLS0+Cgo8IS0tICMjIGNvbnZlcnRpbmcganNvbiBmb3JtYXR0ZWQgZGF0YSB0byBjb2x1bW5zIC0tPgoKIyBSYW5kb21pc2F0aW9uIG9mIHBvc2l0aW9ucwoKT25lIHRoaW5nIHRoYXQgbWlnaHQgYmUgdXNlZnVsIGZvciBhbiBleHBlcmltZW50IGlzIHRvIGNvbXBsZXRlbHkgcmFuZG9taXNlIHRoZSBwb3NpdGlvbiBvZiB0aGUgc3RpbXVsaSBvbiB0aGUgc2NyZWVuLiBJbiB0aGUgcHJldmlvdXMgd29ya3NoZWV0LCB3ZSBwcmVzZW50ZWQgdGhyZWUgaW1hZ2VzIG9uIHRoZSBzY3JlZW4sIHdoaWNoIHdhcyBwcmVkZWZpbmVkIGluIG91ciBzdGltdWxpIGZpbGUuIEUuZy4KCiFbXShpbWFnZXMvaW1hZ2VzX25vdF9yYW5kb21pc2VkLnBuZykKCkJ1dCBpZiB3ZSB3YW50IHRvIGZ1bGx5IHJhbmRvbWlzZSB0aGUgb3JkZXIgZm9yIGVhY2ggcGFydGljaXBhbnQgYW5kIHRyaWFsLCB3ZSBjYW4gdXNlIHNvbWUgY29kZSB0byBtYWtlIHRoaXMgd29yay4KCkluIEpzUHN5Y2ggdGhlcmUgYXJlIHByZWRlZmluZWQgZnVuY3Rpb25zIHdlIGNhbiB1c2UgdGhhdCBoZWxwIHdpdGggcmFuZG9taXNhdGlvbjoKCmBqc1BzeWNoLnJhbmRvbWl6YXRpb24uc2h1ZmZsZWAgY2FuIGJlIHVzZWQgdG8gc2h1ZmZsZSBhbiBhcnJheS4KCmUuZy4gaWYgd2UgaGF2ZSBgW2ltYWdlMSwgaW1hZ2UyLCBpbWFnZTNdYCwgaXQgd2lsbCBwcmVzZW50IHRoZSBpbWFnZXMgaW4gdGhlIHNldCBvcmRlci4gQnV0IGlmIHdlIG1vZGlmeSB0aGUgY29kZSB0byBiZSBganNQc3ljaC5yYW5kb21pemF0aW9uLnNodWZmbGUoW2ltYWdlMSwgaW1hZ2UyLCBpbWFnZTNdKWAgd2Ugc2hvdWxkIGdldCBhIHJhbmRvbWlzZWQgdmVyc2lvbiBvZiB0aGlzIGFycmF5LCBlLmcuIGBbaW1hZ2UzLCBpbWFnZTIsIGltYWdlM11gLgoKV2UgY2FuIGRlbW9uc3RyYXRlIHRoaXMgYnkgbW9kaWZ5aW5nIHRoZSBgbGV4aWNhbF9kZWNpc2lvbl90cmlhbGAgdmFyIGluIG91ciBleHBlcmltZW50OgoKYGBge2pzfQp2YXIgbGV4aWNhbF9kZWNpc2lvbl90cmlhbCA9IHsKICAgIHR5cGU6IGpzUHN5Y2hBdWRpb0J1dHRvblJlc3BvbnNlLAogICAgc3RpbXVsdXM6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiYXVkaW8iKSwKICAgIGNob2ljZXM6IGpzUHN5Y2gucmFuZG9taXphdGlvbi5zaHVmZmxlKFsKICAgICAganNQc3ljaC50aW1lbGluZVZhcmlhYmxlKCJpbWFnZV9sZWZ0IiksCiAgICAgIGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2VfY2VudGVyIiksCiAgICAgIGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2VfcmlnaHQiKQogICAgICBdKSwKICAgIHByb21wdDogIjxwPkNsaWNrIG9uIHRoZSBwaWN0dXJlIHRoYXQgbWF0Y2hlZCB0aGUgc291bmQ8L3A+IiwKICAgIGJ1dHRvbl9odG1sOiAnPGltZyBzcmM9IiVjaG9pY2UlIiB3aWR0aD0xMDAgLz4nLAogICAgZGF0YTogewogICAgICB0YXNrOiAnbGV4aWNhbF9kZWNpc2lvbicsCiAgICAgIGltYWdlX2xlZnQ6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2VfbGVmdCIpLAogICAgICBpbWFnZV9jZW50ZXI6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2VfY2VudGVyIiksCiAgICAgIGltYWdlX3JpZ2h0OiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImltYWdlX3JpZ2h0IiksIAogICAgICByZWFsX3dvcmQ6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgncmVhbF93b3JkJyksCiAgICAgIGNvcnJlY3RfY2hvaWNlOiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoJ2NvcnJlY3RfY2hvaWNlJykKICAgIH0KfTsKCmBgYAoKTm93LCB3ZSBzaG91bGQgc2VlIHRoYXQgdGhlIGltYWdlcyBhcmUgcmFuZG9taXNlZCBpbiB0ZXJtcyBvZiB0aGVpciBwb3NpdGlvbiwgaS5lLiBsZWZ0LCBjZW50ZXIsIHJpZ2h0LgoKIVtdKGltYWdlcy9pbWFnZXNfcmFuZG9taXNlZC5wbmcpCkhvd2V2ZXIsIHdlIG5vdyBoYXZlIGEgcHJvYmxlbSBhcyB3ZSBkbyBub3Qga25vdyB3aGljaCByZXNwb25zZSBpcyB0aGUgY29ycmVjdCBvbmUuCgpJbiBvdXIgc3RpbXVsaSBmaWxlIHdlIGV4cGxpY2l0bHkgY29kZWQgdGhlIGltYWdlIHBvc2l0aW9ucyBhcyBlaXRoZXIgbGVmdCwgY2VudGVyLCBvciByaWdodC4gQnV0IGlmIHdlIHJhbmRvbWlzZSB0aGUgbG9jYXRpb25zLCB3ZSBsb3NlIHRoaXMgaW5mb3JtYXRpb24gYW5kIHRoZXJlIGlzIG5vIGxvZ2dpbmcgb2YgdGhlIG9yZGVyIGluIG91ciBkYXRhIGZpbGUuCgpUbyByZXNvbHZlIHRoaXMgd2UgaGF2ZSB0byBhZGFwdCBvdXIgY29kZSBzbyB0aGF0IEpzUHN5Y2ggbG9ncyB0aGUgb3JkZXIgb2YgdGhlIGltYWdlcyBmb3IgZWFjaCB0cmlhbC4gV2UgY2FuIGRvIHRoaXMgYnkgYWRkaW5nIGEgbmV3IHBhcmFtZXRlciB0byBvdXIgdmFyOgoKYGBge2pzfQpzYXZlX3RyaWFsX3BhcmFtZXRlcnM6IHsKICAgIGNob2ljZXM6IHRydWUKICB9CiAgCmBgYAoKVGhpcyBgc2F2ZV90cmlhbF9wYXJhbWV0ZXJzYCBwYXJhbWV0ZXIgd2lsbCBhbGxvdyB1cyB0byBzYXZlIGFueSBpbmZvcm1hdGlvbiB0aGF0IGlzIHVzZWQgaW4gb3VyIHRyaWFsLiBCeSBzcGVjaWZ5aW5nIGBjaG9pY2VzOiB0cnVlYCB3ZSBhcmUgdGVsbGluZyBKc1BzeWNoIHRvIGFkZCBhIG5ldyB2YXJpYWJsZSB0byBvdXIgZGF0YSB0aGF0IHdpbGwgY29udGFpbiB0aGUgaW5mb3JtYXRpb24gdXNlZCBpbiB0aGUgYGNob2ljZXNgIHBhcmFtZXRlci4KCkl0IHdpbGwgbG9vayBzb21ldGhpbmcgbGlrZSB0aGlzOgoKYFsiY2hlZXNlLnBuZyIsImJhbmFuYS5wbmciLCJzaGVlcC5wbmciXWAKClRoaXMgcHJvdmlkZXMgdXMgd2l0aCB0aGUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIG9yZGVyIGluIHdoaWNoIHRoZSBpbWFnZXMgd2VyZSBwcmVzZW50ZWQsIGkuZS4gdGhlIGxlZnQgaW1hZ2Ugd2FzIHRoZSBjaGVlc2UsIHRoZSBjZW50ZXIgaW1hZ2UgdGhlIGJhbmFuYSBhbmQgdGhlIHJpZ2h0IGltYWdlIHRoZSBzaGVlcC4KClJlbWVtYmVyIHRoYXQgd2hlbiBhIHBhcnRpY2lwYW50IG1ha2VzIGEgY2hvaWNlIGluIHRoaXMgdHJpYWwsIHRoZWlyIHJlc3BvbnNlIHdpbGwgYmUgbG9nZ2VkIGFzIGVpdGhlciBgMGAsIGAxYCwgb3IgYDJgLCB3aGVyZSAwID0gbGVmdCwgMSA9IGNlbnRlciwgYW5kIDIgPSByaWdodC4gSW4gb3VyIHN0aW11bGkgZmlsZSB3ZSBub3cgaGF2ZSB0aGUgaW5jb3JyZWN0IHZhbHVlIGFzIHRoZSBwb3NpdGlvbnMgYXJlIHJhbmRvbWlzZWQsIHNvIG91ciBgY29ycmVjdF9jaG9pY2VgIHZhcmlhYmxlIGlzIG5vdCBsb25nZXIgYWNjdXJhdGUuCgpUbyBkZWFsIHdpdGggdGhpcyB3ZSBzaG91bGQgZmlyc3QgY3JlYXRlIGEgbmV3IHN0aW11bGkgZmlsZS4KCk5vdGUgd2UgaGF2ZSBjaGFuZ2VkIHRoZSBmaXJzdCB0aHJlZSBjb2x1bW5zIHNvIHRoYXQgdGhleSBkbyBub3QgaW5kaWNhdGUgYSBwb3NpdGlvbiwgd2UgaGF2ZSBhbHNvIGNoYW5nZWQgdGhlIGNvcnJlY3RfY2hvaWNlIHNvIHRoYXQgaXQgcmVmZXJzIHRvIHRoZSBpbWFnZSwgbm90IHRoZSBpbmRleCBvZiB0aGUgaW1hZ2UuCgpTYXZlIHRoaXMgZmlsZSBhcyBgbGV4aWNhbF9kZWNpc2lvbl9zdGltdWxpX2ltYWdlX2F1ZGlvX3VwZGF0ZWQuY3N2YAoKYGBge3IgZWNobz1GQUxTRSwgZXZhbD1UUlVFLCB3YXJuaW5nPUZBTFNFfQpkYXRhdGFibGUoZGF0YS5mcmFtZShpbWFnZTEgPSBjKCJmb3gucG5nIiwgImNoZWVzZS5wbmciLCAic2hlZXAucG5nIiwgImJhbmFuYS5wbmciLCAiY2hhaXIucG5nIiwgInBlYS5wbmciLCAiaGVhcnQucG5nIiwgInB1bXBraW4ucG5nIiwgImN1Y3VtYmVyLnBuZyIsICJoaXBwby5wbmciKSwKICAgICAgICAgICAgICAgICAgICAgaW1hZ2UyID0gYygiaGlwcG8ucG5nIiwgImZveC5wbmciLCAiY2hlZXNlLnBuZyIsICJzaGVlcC5wbmciLCAiYmFuYW5hLnBuZyIsICJjaGFpci5wbmciLCAicGVhLnBuZyIsICJoZWFydC5wbmciLCAicHVtcGtpbi5wbmciLCAiY3VjdW1iZXIucG5nIiksCiAgICAgICAgICAgICAgICAgICAgIGltYWdlMyA9IGMoImN1Y3VtYmVyLnBuZyIsICJoaXBwby5wbmciLCAiZm94LnBuZyIsICJjaGVlc2UucG5nIiwgInNoZWVwLnBuZyIsICJiYW5hbmEucG5nIiwgImNoYWlyLnBuZyIsICJwZWEucG5nIiwgImhlYXJ0LnBuZyIsICJwdW1wa2luLnBuZyIpLAogICAgICAgICAgICAgICAgICAgICBhdWRpbyA9IGMoImZveC53YXYiLCAiY2hlZXN4LndhdiIsICJzaGVlcC53YXYiLCAiYmFueG5hLndhdiIsICJiYW5hbmEud2F2IiwgImNoeGlyLndhdiIsICJwZWEud2F2IiwgInB4YS53YXYiLCAiaGVhcnQud2F2IiwgInB1bXBreG4ud2F2IiksCiAgICAgICAgICAgICAgICAgICAgIHJlYWxfd29yZCA9IGMoInllcyIsICJubyIsICJ5ZXMiLCAibm8iLCAieWVzIiwgIm5vIiwgInllcyIsICJubyIsICJ5ZXMiLCAibm8iKSwKICAgICAgICAgICAgICAgICAgICAgY29ycmVjdF9jaG9pY2UgPSBjKCJmb3gucG5nIiwgImNoZWVzZS5wbmciLCAic2hlZXAucG5nIiwgImJhbmFuYS5wbmciLCAiYmFuYW5hLnBuZyIsICJjaGFpci5wbmciLCAicGVhLnBuZyIsICJwZWEucG5nIiwgImhlYXJ0LnBuZyIsICJwdW1wa2luLnBuZyIpKSwgcm93bmFtZXMgPSBGQUxTRSwgZmlsdGVyID0gIm5vbmUiLCBhdXRvSGlkZU5hdmlnYXRpb24gPSBUUlVFLCBvcHRpb25zID0gbGlzdChkb20gPSAndCcsIHBhZ2VMZW5ndGggPSAxMCkpCgpgYGAKCk5vdyB3ZSBjYW4gcHJvY2VzcyB0aGlzIGZpbGUgdG8gYSBgLmpzYCB1c2luZyB0aGUgUiBjb2RlOgoKYGBge3J9CnBhc3RlMCgidmFyIHN0aW11bGkgPSAiLAogICAgICAgdG9KU09OKHJlYWRfZGVsaW0oImxleGljYWxfZGVjaXNpb25fc3RpbXVsaV9pbWFnZV9hdWRpb191cGRhdGVkLmNzdiIpLAogICAgICAgICAgICAgIHByZXR0eT1UUlVFKSwKICAgICAgICI7IikgJT4lCiAgd3JpdGVfZmlsZShmaWxlID0gImxleGljYWxfZGVjaXNpb25fc3RpbXVsaV9pbWFnZV9hdWRpb191cGRhdGVkX0pTT04uanMiKQoKYGBgCgpUaGVuIHdlIHVwbG9hZCBpdCB0byBjb2duaXRpb24ucnVuLgoKUmVtZW1iZXIgdG8gbW9kaWZ5IHlvdXIgY29kZSB0byByZXBsYWNlIGBpbWFnZV9sZWZ0YCB3aXRoIGBpbWFnZTFgLCBgaW1hZ2VfY2VudGVyYCB3aXRoIGBpbWFnZTJgLCBhbmQgYGltYWdlX3JpZ2h0YCB3aXRoIGBpbWFnZTNgLgoKT3VyIGBsZXhpY2FsX2RlY2lzaW9uX3RyaWFsYCBjb2RlIHNob3VsZCBsb29rIGxpa2UgdGhpczoKCmBgYHtqc30KdmFyIGxleGljYWxfZGVjaXNpb25fdHJpYWwgPSB7CiAgICB0eXBlOiBqc1BzeWNoQXVkaW9CdXR0b25SZXNwb25zZSwKICAgIHN0aW11bHVzOiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImF1ZGlvIiksCiAgICBjaG9pY2VzOiBqc1BzeWNoLnJhbmRvbWl6YXRpb24uc2h1ZmZsZShbCiAgICAgIGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2UxIiksCiAgICAgIGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2UyIiksCiAgICAgIGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2UzIikKICAgICAgXSksCiAgICBwcm9tcHQ6ICI8cD5DbGljayBvbiB0aGUgcGljdHVyZSB0aGF0IG1hdGNoZWQgdGhlIHNvdW5kPC9wPiIsCiAgICBidXR0b25faHRtbDogJzxpbWcgc3JjPSIlY2hvaWNlJSIgd2lkdGg9MTAwIC8+JywKICAgIGRhdGE6IHsKICAgICAgdGFzazogJ2xleGljYWxfZGVjaXNpb24nLAogICAgICBpbWFnZTE6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2UxIiksCiAgICAgIGltYWdlMjoganNQc3ljaC50aW1lbGluZVZhcmlhYmxlKCJpbWFnZTIiKSwKICAgICAgaW1hZ2UzOiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImltYWdlMyIpLAogICAgICByZWFsX3dvcmQ6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgncmVhbF93b3JkJyksCiAgICAgIGNvcnJlY3RfY2hvaWNlOiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoJ2NvcnJlY3RfY2hvaWNlJykKICAgIH0sCiAgc2F2ZV90cmlhbF9wYXJhbWV0ZXJzOiB7CiAgICBjaG9pY2VzOiB0cnVlCiAgfQp9OwoKYGBgCgpPdXIgYHByZWxvYWRgIGNvZGUgZm9yIHB1c2hpbmcgaW1hZ2VzIHNob3VsZCBsb29rIGxpa2UgdGhpczoKCmBgYHtqc30KZm9yIChsZXQgaSA9IDA7IGkgPCBzdGltdWxpLmxlbmd0aDsgaSsrKSB7CiAgYWxsX2ltYWdlcy5wdXNoKHN0aW11bGlbaV0uaW1hZ2UxKTsKICBhbGxfaW1hZ2VzLnB1c2goc3RpbXVsaVtpXS5pbWFnZTIpOwogIGFsbF9pbWFnZXMucHVzaChzdGltdWxpW2ldLmltYWdlMyk7Cn07CgpgYGAKCiMgQWRkaW5nIGEgcGFydGljaXBhbnQgSUQKCklmIHdlIHdhbnQgdG8gZW5zdXJlIHRoYXQgd2UgY2FuIGlkZW50aWZ5IG91dCBwYXJ0aWNpcGFudHMgYXMgdW5pcXVlIGluZGl2aWR1YWxzLCBpbiBhbiBhbm9ueW1pc2VkIHdheSBzbyB0aGF0IHRoZWlyIGVtYWlsIGFkZHJlc3Mgb3Igb3RoZXIgZGVtb2dyYXBoaWMgaW5mb3JtYXRpb24gZG9lcyBub3QgbmVlZCB0byBiZSBzaGFyZWQsIHdlIG5lZWQgdG8gYWRkIGEgcGFydGljaXBhbnQgSUQgdG8gb3VyIGRhdGEuCgpXZSBjYW4gZG8gdGhpcyB3aXRoIHRoZSBmb2xsb3dpbmcgY29kZToKCmBgYHtqc30KanNQc3ljaC5kYXRhLmFkZFByb3BlcnRpZXMoe3BhcnRpY2lwYW50X0lEOiBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zbGljZSgyKX0pOwoKYGBgCgpUaGlzIGlzIHdoYXQgdGhlIGNvZGUgZG9lczoKCmBqc1BzeWNoLmRhdGEuYWRkUHJvcGVydGllc2AgaXMgYSBidWlsdCBpbiBKc1BzeWNoIGZ1bmN0aW9uIHRoYXQgY2FuIGFkZCB2YXJpYWJsZXMgdG8gYWxsIG9mIHlvdXIgcm93cyBvZiBkYXRhCgpgcGFydGljaXBhbnRfSURgIGlzIHRoZSBuYW1lIG9mIHRoZSB2YXJpYWJsZSB3ZSB3aWxsIGJlIGFkZGluZyB0byB0aGUgZGF0YSwgdGhpcyBpcyB5b3VyIGRlY2lzaW9uIGFuZCBjYW4gYmUgYW55dGhpbmcgdGhhdCBpcyBjb25zaWRlcmVkIGEgc3VpdGFibGUgdmFyaWFibGUgbmFtZSwgaS5lLiBkbyBub3QgdXNlIHNwYWNlcwoKYE1hdGgucmFuZG9tKClgIGlzIGhvdyB3ZSB3aWxsIGdlbmVyYXRlIGEgcmFuZG9tIGFscGhhLW51bWVyaWMgc3RyaW5nLiAnTWF0aCcgaXMgYSBidWlsdCBpbiBqYXZhc2NyaXB0IHdheSB0byB1c2UgbWF0aCByZWxhdGVkIHBhcmFtZXRlcnMsIGUuZy4gaWYgeW91IHdhbnQgdG8gZ2V0IHRoZSB2YWx1ZSBvZiBwaSB5b3UgY2FuIHVzZSBNYXRoLlBJLiBJZiB3ZSB1c2UgTWF0aC5yYW5kb20oKSBpdCB3aWxsIGNyZWF0ZSBhIHJhbmRvbSBudW1iZXIgPCAxIGFuZCA+IDAKCmAudG9TdHJpbmcoMzYpYCBpcyB0aGUgd2F5IHdlIGNhbiBjb252ZXJ0IHRoZSByYW5kb20gbnVtYmVyIHRvIGEgdmFsdWUgdGhhdCBjb250YWlucyBib3RoIG51bWVyaWMgYW5kIGFscGhhIGNoYXJhY3RlcnMgZS5nLiBpbnN0ZWFkIG9mIDAuMTIzNDUgaXQgd2lsbCBiZSAwLjFhMmIzLiBUaGUgJzM2JyBpcyBpbXBvcnRhbnQsIHRoaXMgaWRlbnRpZmllcyB0aGUgdHlwZXMgb2YgY2hhcmFjdGVycyB3ZSB3aWxsIGhhdmUgd2hlbiBjb252ZXJ0aW5nIHRvIGEgc3RyaW5nLCBpdCBpcyAzNiBhcyB0aGF0IGlzIHRoZSB3YXkgdG8gZGVjbGFyZSBiYXNlLTM2LCB3aGljaCBpcyBqdXN0IGEgd2F5IHRvIHNheSBvbmx5IGluY2x1ZGUgWzAtOV0gYW5kIFthLXpdIHZhbHVlcwoKYC5zbGljZSgyKWAgaXMgaG93IHdlIGNhbiByZW1vdmUgdGhlIGZpcnN0IHR3byBjaGFyYWN0ZXJzIG9mIHRoZSBzdHJpbmcsIGkuZS4gdGhlICcwLicgd2hpY2ggaXMgdGhlcmUgYXMgTWF0aC5yYW5kb20gaXMgYWx3YXlzIDwgMQoKVGhlIHJlc3VsdCB3aWxsIGJlIHNvbWV0aGluZyBsaWtlIGA1OWY0endwZ2xra2AKCkkgd291bGQgcGVyc29uYWxseSBhZGQgdGhpcyBjb2RlIHRvd2FyZHMgdGhlIGJlZ2lubmluZyBvZiB5b3VyIHNjcmlwdCwgZS5nLiBhZnRlciB5b3UgZGVjbGFyZSBgdmFyIGpzUHN5Y2ggPSBpbml0SnNQc3ljaCgpYAoKIyBUaGUgZnVsbCBleHBlcmltZW50CgpUaGUgZnVsbCBleHBlcmltZW50IGNvZGUgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge2pzfQovLyAtLS0tLS0tLQovLyBUaXRsZTogRGVtbyBleHBlcmltZW50Ci8vIGpzUHN5Y2ggdmVyc2lvbjogNy4zLjEKLy8gZGF0ZTogW3RvZGF5XQovLyBhdXRob3I6IFt5b3VyIG5hbWVdCi8vLS0tLS0tLS0tLQoKLy8gaW5pdGl0YXRlIGpzcHN5Y2gKdmFyIGpzUHN5Y2ggPSBpbml0SnNQc3ljaCgpOwoKanNQc3ljaC5kYXRhLmFkZFByb3BlcnRpZXMoe3BhcnRpY2lwYW50X0lEOiBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zbGljZSgyKX0pOwoKLy8gZnVsbCBzY3JlZW4KdmFyIGVudGVyX2Z1bGxzY3JlZW4gPSB7CiAgdHlwZToganNQc3ljaEZ1bGxzY3JlZW4sCiAgZnVsbHNjcmVlbl9tb2RlOiB0cnVlLAogIG1lc3NhZ2U6ICc8cD5zb21lIG1lc3NhZ2U8L3A+JywKICBidXR0b25fbGFiZWw6ICdlbnRlciBmdWxsIHNjcmVlbiBtb2RlJywKICBkYXRhOiB7CiAgICAgIHRhc2s6ICdmdWxsc2NyZWVuJwogICAgfQp9OwoKLy8gYSByZWFsbHkgc2ltcGxlIGluc3RydWN0aW9ucyBwYWdlIHdpdGggYSBsb2dvCnZhciBpbnN0cnVjdGlvbnNfcGFnZSA9IHsKICAgIHR5cGU6IGpzUHN5Y2hJbnN0cnVjdGlvbnMsCiAgICBwYWdlczogWwogICAgJzxpbWcgc3JjPSJodHRwczovL3NpdGVzLmZmLmN1bmkuY3ovdWNqdGsvd3AtY29udGVudC91cGxvYWRzL3NpdGVzLzU3LzIwMjAvMTEvVXN0YXZfY2Vza2UlRTIlOTUlQTAlQzMlQkNob19qYXp5a2FfYV90ZW9yaWVfa29tdW5pa2FjZS5zdmciIHdpZHRoPSI0MDAiIGhlaWdodD0iMTQwIj48L2JyPjxocj48ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBsZWZ0OyBtYXJnaW4tcmlnaHQ6IDE1MHB4OyBtYXJnaW4tbGVmdDogMTUwcHg7Ij48aDI+SW5zdHJ1Y3Rpb25zPC9oMj48cD5JbiB0aGlzIGV4cGVyaW1lbnQgeW91IHdpbGwgc2VlIGEgd29yZCBvbiB0aGUgc2NyZWVuLjwvcD48cD48Yj5JZiB5b3UgdGhpbmsgdGhlIHdvcmQgaXMgYSByZWFsIHdvcmQgcHJlc3MgdGhlICJkIiBrZXksIGlmIHlvdSBkbyBub3QgdGhpbmsgaXQgaXMgYSByZWFsIHdvcmQsIHByZXNzIHRoZSAiaCIga2V5LjwvYj48L3A+PHA+UHJlc3MgbmV4dCB0byBjb250aW51ZS48L2Rpdj4nLAogICAgJ0luIHRoaXMgZXhwZXJpbWVudCB5b3Ugd2lsbC4uLicKICAgIF0sCiAgICBzaG93X2NsaWNrYWJsZV9uYXY6IHRydWUsCiAgICBrZXlfZm9yd2FyZDogJ0VudGVyJywKICAgIGtleV9iYWNrd2FyZDogJ0Fycm93TGVmdCcsCiAgICBhbGxvd19iYWNrd2FyZDogdHJ1ZSwKICAgIHNob3dfY2xpY2thYmxlX25hdjogdHJ1ZSwKICAgIGJ1dHRvbl9sYWJlbF9wcmV2aW91czogJ2JhY2snLAogICAgYnV0dG9uX2xhYmVsX25leHQ6ICduZXh0JywKICBkYXRhOiB7CiAgICAgIHRhc2s6ICdpbnN0cnVjdGlvbnMnCiAgICB9Cn07CgovLyBhIHJlYWxseSBiYWQgY29uc2VudCBmb3JtIHBhZ2UKdmFyIGNvbnNlbnRfcGFnZSA9IHsKICAgIHR5cGU6IGpzUHN5Y2hJbnN0cnVjdGlvbnMsCiAgICBwYWdlczogWwogICAgJ0RldGFpbHMgb2YgaW5mb3JtZWQgY29uc2VudC4uLicsCiAgICBdLAogICAgc2hvd19jbGlja2FibGVfbmF2OiB0cnVlLAogICAgYWxsb3dfYmFja3dhcmQ6IGZhbHNlLAogICAgYnV0dG9uX2xhYmVsX25leHQ6ICdJIGNvbnNlbnQgdG8gcGFydGljaXBhdGluZyBpbiB0aGlzIGV4cGVyaW1lbnQnLAogIGRhdGE6IHsKICAgICAgdGFzazogJ2NvbnNlbnQnCiAgICB9Cn07CgovLyBhIGRlbW9ncmFwaGljcyBwYWdlIHRoYXQgaXMgdmVyeSBiYWQKdmFyIGRlbW9ncmFwaGljc19wYWdlID0gewogIHR5cGU6IGpzUHN5Y2hTdXJ2ZXlIdG1sRm9ybSwKICBodG1sOiAnPGRpdiBzdHlsZT0idGV4dC1hbGlnbjogbGVmdDsiPicrCiAgJzxwPlF1ZXN0aW9uIDEgPC9wPjxpbnB1dCBpZD0iUTEiIG5hbWU9IlExIiB0eXBlPSJ0ZXh0IiByZXF1aXJlZC8+JysKICAnPHA+UXVlc3Rpb24gMiA8L3A+PGlucHV0IGlkPSJRMiIgbmFtZT0iUTIiIHR5cGU9Im51bWJlciIgLz4nKwogICc8cD5RdWVzdGlvbiAzIDwvcD48aW5wdXQgaWQ9IlEzIiBuYW1lPSJRMyIgdHlwZT0iZW1haWwiIC8+JysKICAnPHA+UXVlc3Rpb24gNDwvcD4nKwogICc8aW5wdXQgdHlwZT0icmFkaW8iIGlkPSJRNCIgbmFtZT0iUTQiIHZhbHVlPSJhIj48bGFiZWw+YTwvbGFiZWw+PGJyPicrCiAgJzxpbnB1dCB0eXBlPSJyYWRpbyIgaWQ9IlE0IiBuYW1lPSJRNCIgdmFsdWU9ImIiPjxsYWJlbD5iPC9sYWJlbD48YnI+JysKICAnPGlucHV0IHR5cGU9InJhZGlvIiBpZD0iUTQiIG5hbWU9IlE0IiB2YWx1ZT0iYyI+PGxhYmVsPmM8L2xhYmVsPjxicj4nKwogICc8cD5RdWVzdGlvbiA1PC9wPicrCiAgJzxpbnB1dCB0eXBlPSJjaGVja2JveCIgaWQ9IlE1IiBuYW1lPSJRNSIgdmFsdWU9ImEiPjxsYWJlbD5hPC9sYWJlbD48YnI+JysKICAnPGlucHV0IHR5cGU9ImNoZWNrYm94IiBpZD0iUTUiIG5hbWU9IlE1IiB2YWx1ZT0iYiI+PGxhYmVsPmI8L2xhYmVsPjxicj4nKwogICc8aW5wdXQgdHlwZT0iY2hlY2tib3giIGlkPSJRNSIgbmFtZT0iUTQiIHZhbHVlPSJjIj48bGFiZWw+YzwvbGFiZWw+PGJyPicrCiAgJzxwPlF1ZXN0aW9uIDY8L3A+JysKICBgPHRleHRhcmVhIGlkPSJmZWVkYmFja19jb21tZW50cyIgbmFtZT0iZmVlZGJhY2tfY29tbWVudHMiIHJvd3M9IjQiIGNvbHM9IjUwIi8+PC90ZXh0YXJlYT5gKwogICc8L2JyPjwvZGl2PicsCiAgYnV0dG9uX2xhYmVsOiAnbmV4dCcsCiAgYXV0b2ZvY3VzOiAnUTEnLAogIGRhdGE6IHsKICAgICAgdGFzazogJ2RlbW9ncmFwaGljcycKICAgIH0KfTsKCi8vIG1haW4gdHJpYWwgd2hlcmUgaW1hZ2VzIGFuZCBhdWRpbyBhcmUgcHJlc2VudGVkCnZhciBsZXhpY2FsX2RlY2lzaW9uX3RyaWFsID0gewogICAgdHlwZToganNQc3ljaEF1ZGlvQnV0dG9uUmVzcG9uc2UsCiAgICBzdGltdWx1czoganNQc3ljaC50aW1lbGluZVZhcmlhYmxlKCJhdWRpbyIpLAogICAgY2hvaWNlczoganNQc3ljaC5yYW5kb21pemF0aW9uLnNodWZmbGUoWwogICAgICBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImltYWdlMSIpLAogICAgICBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImltYWdlMiIpLAogICAgICBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImltYWdlMyIpCiAgICAgIF0pLAogICAgcHJvbXB0OiAiPHA+Q2xpY2sgb24gdGhlIHBpY3R1cmUgdGhhdCBtYXRjaGVkIHRoZSBzb3VuZDwvcD4iLAogICAgYnV0dG9uX2h0bWw6ICc8aW1nIHNyYz0iJWNob2ljZSUiIHdpZHRoPTEwMCAvPicsCiAgICBkYXRhOiB7CiAgICAgIHRhc2s6ICdsZXhpY2FsX2RlY2lzaW9uJywKICAgICAgaW1hZ2UxOiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoImltYWdlMSIpLAogICAgICBpbWFnZTI6IGpzUHN5Y2gudGltZWxpbmVWYXJpYWJsZSgiaW1hZ2UyIiksCiAgICAgIGltYWdlMzoganNQc3ljaC50aW1lbGluZVZhcmlhYmxlKCJpbWFnZTMiKSwKICAgICAgcmVhbF93b3JkOiBqc1BzeWNoLnRpbWVsaW5lVmFyaWFibGUoJ3JlYWxfd29yZCcpLAogICAgICBjb3JyZWN0X2Nob2ljZToganNQc3ljaC50aW1lbGluZVZhcmlhYmxlKCdjb3JyZWN0X2Nob2ljZScpCiAgICB9LAogIHNhdmVfdHJpYWxfcGFyYW1ldGVyczogewogICAgY2hvaWNlczogdHJ1ZQogIH0KfTsKCi8vIGZpeGF0aW9uIHRyaWFsCnZhciBmaXhhdGlvbl90cmlhbCA9IHsKICB0eXBlOiBqc1BzeWNoSHRtbEtleWJvYXJkUmVzcG9uc2UsCiAgc3RpbXVsdXM6ICc8ZGl2IHN0eWxlPSJmb250LXNpemU6MzBwdDsiPis8L2Rpdj4nLAogIGNob2ljZXM6ICdOT19LRVlTJywKICBwcm9tcHQ6ICc8ZGl2IHN0eWxlPSJmb250LXNpemU6MTBwdDsgcG9zaXRpb246cmVsYXRpdmU7IGJvdHRvbToxNTBweDsiPjxici8+PC9kaXY+JywKICB0cmlhbF9kdXJhdGlvbjogMjAwMCwKICBkYXRhOiB7CiAgICAgIHRhc2s6ICdmaXhhdGlvbicKICAgIH0KfTsKCi8vIGNvbWJpbmVkIGZpeGF0aW9uIGFuZCBtYWluIHRyaWFsIHdpdGggdGltZWxpbmUgdmFyaWFibGVzIGFuZCByYW5kb21pc2F0aW9uCnZhciBsZXhpY2FsX2RlY2lzaW9uX2NvbWJpbmVkID0gewogIHRpbWVsaW5lOiBbZml4YXRpb25fdHJpYWwsIGxleGljYWxfZGVjaXNpb25fdHJpYWxdLAogIHRpbWVsaW5lX3ZhcmlhYmxlczogc3RpbXVsaSwKICByYW5kb21pemVfb3JkZXI6IHRydWUKfTsKCi8vIGZlZWRiYWNrIHBhZ2UKdmFyIGZlZWRiYWNrX3BhZ2UgPSB7CiAgdHlwZToganNQc3ljaFN1cnZleUh0bWxGb3JtLAogIGh0bWw6ICc8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBsZWZ0OyI+JysKICAnPHA+VGhhdCBpcyB0aGUgZW5kIG9mIHRoZSBleHBlcmltZW50LjwvcD4nKwogICc8cD5Ib3cgZWFzeSBkaWQgeW91IGZpbmQgdGhlIGV4cGVyaW1lbnQ/IDwvcD4nKwogICc8aW5wdXQgdHlwZT0icmFkaW8iIGlkPSJmZWVkYmFja19lYXNlIiBuYW1lPSJmZWVkYmFja19lYXNlIiB2YWx1ZT0iZGlmZmljdWx0Ij48bGFiZWw+ZGlmZmljdWx0PC9sYWJlbD48YnI+JysKICAnPGlucHV0IHR5cGU9InJhZGlvIiBpZD0iZmVlZGJhY2tfZWFzZSIgbmFtZT0iZmVlZGJhY2tfZWFzZSIgdmFsdWU9ImVhc3kiPjxsYWJlbD5lYXN5PC9sYWJlbD48YnI+JysKICAnPHA+SWYgeW91IGhhdmUgYW55IGZlZWRiYWNrIGFib3V0IHRoZSBleHBlcmltZW50LCBwbGVhc2Ugd3JpdGUgeW91ciBjb21tZW50cyBiZWxvdy48L3A+JysKICBgPHRleHRhcmVhIGlkPSJmZWVkYmFja19jb21tZW50cyIgbmFtZT0iZmVlZGJhY2tfY29tbWVudHMiIHJvd3M9IjQiIGNvbHM9IjUwIi8+PC90ZXh0YXJlYT5gKwogICc8L2JyPjwvZGl2PicsCiAgYnV0dG9uX2xhYmVsOiAnU3VibWl0IHJlc3VsdHMnLAogIGF1dG9mb2N1czogJ2ZlZWRiYWNrX2Vhc2UnLAogIGRhdGE6IHsKICAgICAgdGFzazogJ2ZlZWRiYWNrJwogICAgfQp9OwoKLy8gZ2V0IGltYWdlIGZpbGVuYW1lcwp2YXIgYWxsX2ltYWdlcyA9IFtdOwoKZm9yIChsZXQgaSA9IDA7IGkgPCBzdGltdWxpLmxlbmd0aDsgaSsrKSB7CiAgYWxsX2ltYWdlcy5wdXNoKHN0aW11bGlbaV0uaW1hZ2UxKTsKICBhbGxfaW1hZ2VzLnB1c2goc3RpbXVsaVtpXS5pbWFnZTIpOwogIGFsbF9pbWFnZXMucHVzaChzdGltdWxpW2ldLmltYWdlMyk7Cn07CgovLyBnZXQgYXVkaW8gZmlsZW5hbWVzCnZhciBhbGxfYXVkaW8gPSBbXTsKCmZvciAobGV0IGkgPSAwOyBpIDwgc3RpbXVsaS5sZW5ndGg7IGkrKykgewogIGFsbF9hdWRpby5wdXNoKHN0aW11bGlbaV0uYXVkaW8pOwp9OwoKLy8gcHJlbG9hZCB0cmlhbAp2YXIgcHJlbG9hZCA9IHsKICAgIHR5cGU6IGpzUHN5Y2hQcmVsb2FkLAogICAgaW1hZ2VzOiBbYWxsX2ltYWdlc10sCiAgICBhdWRpbzogW2FsbF9hdWRpb10sCiAgZGF0YTogewogICAgICB0YXNrOiAncHJlbG9hZCcKICAgIH0KfTsKCi8vIG1ha2UgdGhlIHRpbWVsaW5lIGFycmF5CnZhciB0aW1lbGluZSA9IFtdOwoKLy8gcHVzaCB0aGUgdHJpYWxzIHRvIHRoZSB0aW1lbGluZSBpbiB0aGlzIG9yZGVyCnRpbWVsaW5lLnB1c2gocHJlbG9hZCk7Ci8vIHRpbWVsaW5lLnB1c2goZW50ZXJfZnVsbHNjcmVlbik7CnRpbWVsaW5lLnB1c2goaW5zdHJ1Y3Rpb25zX3BhZ2UpOwp0aW1lbGluZS5wdXNoKGNvbnNlbnRfcGFnZSk7CnRpbWVsaW5lLnB1c2goZGVtb2dyYXBoaWNzX3BhZ2UpOwoKdGltZWxpbmUucHVzaChsZXhpY2FsX2RlY2lzaW9uX2NvbWJpbmVkKTsKCnRpbWVsaW5lLnB1c2goZmVlZGJhY2tfcGFnZSk7CgovL3J1biB0aGUgZXhwZXJpbWVudApqc1BzeWNoLnJ1bih0aW1lbGluZSk7CgpgYGAKClJ1biB0aHJvdWdoIHRoZSBleHBlcmltZW50IHVzaW5nIHRoZSBleHBlcmltZW50IGxpbmsgKGUuZy4gaHR0cHM6Ly9sazZ3b3M0b2ZyLmNvZ25pdGlvbi5ydW4pIGFuZCBvYnRhaW4gc29tZSBkYXRhLgoKTm93IHdlIGNhbiBwcm9jZXNzIHRoZSBkYXRhLgoKIyBEb3dubG9hZGluZyB0aGUgZGF0YQoKT25jZSB5b3UgaGF2ZSBzZW50IG91dCB5b3VyIGxpbmsgdG8gcGFydGljaXBhbnRzLCB5b3Ugd2lsbCBzZWUgdGhhdCB0aGVyZSBhcmUgbm93IHNvbWUgcmVzcG9uc2VzIGluIHRoZSBleHBlcmltZW50IGRhc2hib2FyZCwgeW91IGNhbiBkb3dubG9hZCB0aGUgZGF0YSB1c2luZyB0aGUgYERvd25sb2FkIGRhdGFgIG9wdGlvbjoKCiFbXShpbWFnZXMvZGFzaGJvYXJkLnBuZykKCk9uIHRoZSBuZXh0IHBhZ2UgeW91IHdpbGwgc2VlIHNvbWUgb3B0aW9ucywgc2V0IHRoZXNlIGFzOgoKRGF0YSBzZXQ6IGFsbCBydW5zCgpEYXRhIGVuY29kaW5nOiBDU1YKCkZpbGUgZm9ybWF0OiBaaXAgZm9sZGVyCgpJbmRpdmlkdWFsIGZpbGVuYW1lIGZvcm1hdDogcGFydGljaXBhbnRfSUQKClRoZW4gY2xpY2sgdGhlIGBkb3dubG9hZGAgYnV0dG9uCgohW10oaW1hZ2VzL2Rvd25sb2FkX2RhdGEucG5nKQoKWW91IHNob3VsZCBub3cgaGF2ZSBhIC56aXAgZm9sZGVyIHdpdGggYWxsIG9mIHRoZSBkYXRhLCB1bnppcCBpdCBhbmQgeW91IGNhbiBhY2Nlc3MgdGhlIGZpbGVzLgoKIVtdKGltYWdlcy9kYXRhX2ZvbGRlci5wbmcpCgojIFByb2Nlc3NpbmcgZGF0YSBpbiBSCgpGaXJzdCB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBmb2xsb3dpbmcgbGlicmFyaWVzOgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGpzb25saXRlKQoKYGBgCgojIyB0aWR5dmVyc2UKClNlZSB0aGUgYHRpZHl2ZXJzZWAgZm9sZGVyIGZvciB3b3Jrc2hlZXQuCgojIyBSZWFkaW5nIGluIG11bHRpcGxlIGZpbGVzCgpXZSBjYW4gcmV0dXJuIHRvIG91ciBleHBlcmltZW50YWwgZGF0YSBmcm9tIGNvZ25pdGlvbi5ydW4gd2hlcmUgd2UgaGF2ZSBpbmRpdmlkdWFsIGZpbGVzIGZvciBlYWNoIHBhcnRpY2lwYW50LgoKV2UgY2FuIHVzZSBSIHRvIHJlYWQgaW4gdGhlc2UgZmlsZXMgYW5kIGJpbmQgdGhlbSBpbnRvIG9uZS4KCkZpcnN0IHNldCB5b3VyIHdvcmtpbmcgZGlyZWN0b3J5IHRvIHRoZSBsb2NhdGlvbiB3aGVyZSB5b3UgaGF2ZSB0aGUgZmlsZXMuIFdlIGNhbiB1c2UgdGhlIGRhdGEgc3RvcmVkIG9uIE9uZURyaXZlIHNvIHRoYXQgZXZlcnlib2R5IGlzIHdvcmtpbmcgd2l0aCB0aGUgc2FtZSBkYXRhLgoKQ2hhbmdlIHRoZSBmaWxlIHBhdGggdG8gdGhlIGB3ZWVrXzhgIGZvbGRlcgoKYGBge3J9CnNldHdkKCIvVXNlcnMvamFtZXMvTGlicmFyeS9DbG91ZFN0b3JhZ2UvT25lRHJpdmUtRmlsb3pvZmlja2HMgWZha3VsdGEsVW5pdmVyeml0YUthcmxvdmEvQ29uZHVjdGluZ19leHBlcmltZW50YWxfcmVzZWFyY2hfb25saW5lXzIwMjQvTGVjdHVyZXMvd2Vla184LyIpCgpgYGAKCk5vdyB3ZSBjYW4gdXNlIHNvbWUgY29kZSB0byByZWFkIGluIGFsbCB0aGUgZmlsZXMgaW4gdGhlIGBkYXRhYCBmb2xkZXIgYW5kIGJpbmQgdGhlbSB0b2dldGhlcgoKYGBge3IgZXZhbD1GQUxTRX0KYXVkaW9fZXhwZXJpbWVudCA8LSBsaXN0LmZpbGVzKHBhdGggPSAiZGF0YS8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICIuY3N2IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY3Vyc2l2ZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkgJT4lIAogICAgbWFwX2RmKH5yZWFkX2NzdigueCkpICU+JQogICAgdHlwZV9jb252ZXJ0KCkKCmBgYAoKSGVyZSBpcyB3aGF0IHRoZSBjb2RlIGRvZXM6CgpgYXVkaW9fZXhwZXJpbWVudGAgaXMgdGhlIG5hbWUgb2YgdGhlIG9iamVjdCB3aGVyZSB3ZSB3aWxsIHN0b3JlIHRoZSBkYXRhCgpgPC1gIGlzIHRoZSB3YXkgd2UgYXNzaWduIG9iamVjdHMgaW4gUgoKYGxpc3QuZmlsZXNgIGlzIGhvdyB3ZSB3aWxsIGdldCBhbGwgdGhlIG5hbWVzIG9mIHRoZSBmaWxlcyB3ZSB3aWxsIGJlIHByb2Nlc3NpbmcKCmBwYXRoID0gImRhdGEvImAgaXMgaG93IHdlIHNwZWNpZnkgdGhlIGZvbGRlciB3aGVyZSB0aGUgZmlsZSB3ZSB3YW50IGFyZSBsb2NhdGVkCgpgcGF0dGVybiA9ICIuY3N2ImAgaXMgaG93IHdlIHNwZWNpZnkgdGhhdCB3ZSBvbmx5IHdhbnQgZmlsZXMgd2l0aCAuY3N2IGluIHRoZWlyIG5hbWUKCmByZWN1cnNpdmUgPSBUUlVFYCBpcyBob3cgd2Ugd2lsbCBsb29rIHRocm91Z2ggZm9sZGVycyB3aXRoaW4gdGhlIGZvbGRlciBmb3IgZmlsZXMsIGUuZy4gaWYgd2UgaGFkIHN1YmZvbGRlcnMgY2FsbGVkICduZXdfZGF0YScgYW5kICdvbGRfZGF0YScgd2l0aGluIG91ciAnZGF0YScgZm9sZGVyIHRoZSBjb2RlIHdpbGwgbG9vayBmb3IgLmNzdiBmaWxlcyBpbiB0aGVzZSBzdWJmb2xkZXJzLCBpZiBpdCBpcyBzZXQgdG8gRkFMU0UgaXQgd2lsbCBvbmx5IGxvb2sgaW4gdGhlICdkYXRhJyBmb2xkZXIgYW5kIG5vdCB3aXRoaW4gYW55IHN1YmZvbGRlcnMuIFRoaXMgaXMgdXNlZnVsIGFzIHdlIGRvIGhhdmUgYSBzdWJmb2xkZXIgY2FsbGVkIGBhdWRpb2AKCmBmdWxsLm5hbWVzID0gVFJVRWAgaXMgaG93IHdlIHdpbGwgZ2V0IHRoZSBmdWxsIGZpbGUgcGF0aCwgZS5nLiBpbnN0ZWFkIG9mIGp1c3QgYDRjZWRxcjRoNnBlLmNzdmAsIHdlIHdpbGwgaGF2ZSBgL1VzZXJzL2phbWVzL0xpYnJhcnkvQ2xvdWRTdG9yYWdlL09uZURyaXZlLUZpbG96b2ZpY2thzIFmYWt1bHRhLFVuaXZlcnppdGFLYXJsb3ZhL0NvbmR1Y3RpbmdfZXhwZXJpbWVudGFsX3Jlc2VhcmNoX29ubGluZV8yMDI0L0xlY3R1cmVzL3dlZWtfOC9kYXRhL2F1ZGlvLzRjZWRxcjRoNnBlLmNzdmAuIFRoaXMgbWFrZXMgcmVhZGluZyBpbiB0aGUgZmlsZXMgZWFzaWVyCgpgbWFwX2RmKH5yZWFkX2NzdigueCkpYCBpcyBub3cgZ29pbmcgdG8gaXRlcmF0aXZlbHkgcmVhZCBpbiB0aGUgLmNzdiBmaWxlcyB1c2luZyB0aGUgZmlsZW5hbWVzIHdlIGdvdCBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0IG9mIHRoZSBjb2RlLiBgbWFwX2RmYCB3aWxsIG1ha2Ugc3VyZSBldmVyeXRoaW5nIGlzIGJpbmRlZCB0b2dldGhlciwgYH5yZWFkX2NzdmAgd2lsbCByZWFkIGluIHRoZSBmaWxlbmFtZXMsIGFuZCBgLnhgIGlzIHdoZXJlIHRoZSBmaWxlbmFtZXMgd2lsbCBiZSByZWFkIGZyb20sIHdoaWNoIGlzIGEgcXVpY2sgd2F5IHRvIHJlZmVyIHRvIGFsbCB0aGUgZmlsZW5hbWVzIGxpc3RlZCBpbiB0aGUgcHJldmlvdXMgcGFydCBvZiB0aGUgY29kZQoKYHR5cGVfY29udmVydGAgaXMgdXNlZCB0byBtYWtlIHN1cmUgYWxsIHRoZSBjb2x1bW5zIGFyZSBpbiB0aGUgc2FtZSBmb3JtYXQsIGUuZy4gYHBhcnRpY2lwYW50X0lEYCBpcyBhbHdheXMgYSBjaGFyYWN0ZXIgdmFyaWFibGUKCk5vdyB3ZSBzaG91bGQgaGF2ZSB0aGUgZGF0YSByZWFkeSB0byB1c2UuCgojIyBQcm9jZXNzaW5nCgpXZSBjYW4gbm93IGFwcGx5IHNvbWUgcHJvY2Vzc2luZyBzdGVwcyB0byBvdXIgZGF0YS4KClNlZSBpZiB5b3UgY2FuIHVuZGVyc3RhbmQgdGhlIGNvZGUgYmVsb3cgYW5kIGludGVycHJldCB3aGF0IGl0IGlzIGRvaW5nLgoKYGBge3IgZXZhbD1GQUxTRX0KZGVjaXNpb25fZGF0YSA8LSBhdWRpb19leHBlcmltZW50ICU+JQogIGZpbHRlcih0YXNrID09ICJsZXhpY2FsX2RlY2lzaW9uIikgJT4lCiAgc2VsZWN0KHJlY29yZGVkX2F0LCBydDpjaG9pY2VzKSAlPiUKICBtdXRhdGUoY2hvaWNlID0gc3RyX3JlbW92ZV9hbGwoY2hvaWNlcywgJ1xcInxcXFt8XFxdJykpICU+JQogIHNlcGFyYXRlKGNob2ljZSwgaW50byA9IGMoImltYWdlXzAiLCAiaW1hZ2VfMSIsICJpbWFnZV8yIiksIHNlcCA9ICIsIikgJT4lCiAgbXV0YXRlKHJlc3BvbnNlX2ltYWdlID0gaWZlbHNlKHJlc3BvbnNlID09IDAsIGltYWdlXzAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShyZXNwb25zZSA9PSAxLCBpbWFnZV8xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJlc3BvbnNlID09IDIsIGltYWdlXzIsIE5BKSkpLAogICAgICAgICByZXNwb25zZV9jb3JyZWN0ID0gaWZlbHNlKHJlc3BvbnNlX2ltYWdlID09IGNvcnJlY3RfY2hvaWNlLCAxLCAwKSkKCmBgYAoKIyMgQ29udmVydCBKU09OIHRvIGNvbHVtbnMKClNvbWUgZGF0YSB3aWxsIGJlIHN0b3JlZCBpbiBhIEpTT04gZm9ybWF0LCB3aGljaCBpcyBub3QgdG9vIGVhc3kgdG8gdXNlIGluIFIuCgpMb29rIGF0IHRoZSBkYXRhIGZvciBgcmVzcG9uc2VgIHdoZW4gYHRhc2sgPT0gImRlbW9ncmFwaGljcyJgCgpgYGB7cn0KYXVkaW9fZXhwZXJpbWVudCAlPiUKICBmaWx0ZXIodGFzayA9PSAiZGVtb2dyYXBoaWNzIikgJT4lCiAgc2VsZWN0KHBhcnRpY2lwYW50X0lELCByZXNwb25zZSkKCmBgYAoKV2UgY2FuIGNvbnZlcnQgdGhpcyBKU09OIGRhdGEgdG8gY29sdW1ucywgc28gdGhhdCB3ZSBoYXZlIGRhdGEgdGhhdCBpcyBlYXNpZXIgdG8gdXNlLgoKV2UgY2FuIHVzZSB0aGlzIGNvZGUgdG8gbWFrZSBhbiBvYmplY3QgdGhhdCBjb250YWlucyB0aGUgZGVtb2dyYXBoaWMgcXVlc3Rpb24gZGF0YToKCmBgYHtyfQpkZW1vZ3JhcGhpY3MgPC0gYXVkaW9fZXhwZXJpbWVudCAlPiUKICBmaWx0ZXIodGFzayA9PSAiZGVtb2dyYXBoaWNzIikgJT4lCiAgc2VsZWN0KHBhcnRpY2lwYW50X0lELCByZXNwb25zZSkgJT4lCiAgbXV0YXRlKHJlc3BvbnNlID0gbWFwKHJlc3BvbnNlLCB+IGZyb21KU09OKC4pICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSkpICU+JQogIHVubmVzdChyZXNwb25zZSkKCmBgYAoKCgoKYGBge3IgZWNobz1GQUxTRSwgZXZhbD1UUlVFLCB3YXJuaW5nPUZBTFNFfQpodG1sdG9vbHM6OnRhZ3Mkc2NyaXB0KHNyYyA9ICJqcy90cmFuc2xhdGUuanMiKQojIGh0bWx0b29sczo6dGFncyRzY3JpcHQoc3JjID0gImpzL2luZm9ib3guanMiKQpodG1sdG9vbHM6OnRhZ3Mkc2NyaXB0KHNyYz0iLy90cmFuc2xhdGUuZ29vZ2xlLmNvbS90cmFuc2xhdGVfYS9lbGVtZW50LmpzP2NiPWdvb2dsZVRyYW5zbGF0ZUVsZW1lbnRJbml0IikKCmh0bWx0b29sczo6dGFnTGlzdCgKICB4YXJpbmdhbkV4dHJhOjp1c2VfY2xpcGJvYXJkKAogICAgYnV0dG9uX3RleHQgPSAiPGkgY2xhc3M9XCJmYSBmYS1jbGlwYm9hcmRcIiBzdHlsZT1cImZvbnQtc2l6ZTogMjVweFwiPjwvaT4iLAogICAgc3VjY2Vzc190ZXh0ID0gIjxpIGNsYXNzPVwiZmEgZmEtY2hlY2tcIiBzdHlsZT1cImNvbG9yOiAjOTBCRTZEOyBmb250LXNpemU6IDI1cHhcIj48L2k+IiwKICApLAogIHJtYXJrZG93bjo6aHRtbF9kZXBlbmRlbmN5X2ZvbnRfYXdlc29tZSgpCikKCmBgYAoKYGBge2pzIGVjaG89RkFMU0UsIGV2YWw9VFJVRX0KdmFyIGNvbGwgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCJjb2xsYXBzaWJsZTEiKTsKdmFyIGk7Cgpmb3IgKGkgPSAwOyBpIDwgY29sbC5sZW5ndGg7IGkrKykgewogIGNvbGxbaV0uYWRkRXZlbnRMaXN0ZW5lcigiY2xpY2siLCBmdW5jdGlvbigpIHsKICAgIHRoaXMuY2xhc3NMaXN0LnRvZ2dsZSgiYWN0aXZlMSIpOwogICAgdmFyIGNvbnRlbnQgPSB0aGlzLm5leHRFbGVtZW50U2libGluZzsKICAgIGlmIChjb250ZW50LnN0eWxlLm1heEhlaWdodCl7CiAgICAgIGNvbnRlbnQuc3R5bGUubWF4SGVpZ2h0ID0gbnVsbDsKICAgIH0gZWxzZSB7CiAgICAgIGNvbnRlbnQuc3R5bGUubWF4SGVpZ2h0ID0gY29udGVudC5zY3JvbGxIZWlnaHQgKyAicHgiOwogICAgfQogIH0pOwp9CgpgYGAK